using System;
using System.Collections;
using System.Text.RegularExpressions;
using System.IO;
using System.Security;
using System.Collections.Specialized;
//using CommandLine.Utility;

//Traditionally grep stands for "Global Regular Expression Print".
//Global means that an entire file is searched. 
//Regular Expression means that a regular expression string is used to establish a search pattern. 
//Print means that the command will display its findings. 
//Simply put, grep searches an entire file for the pattern you want and displays its findings.
//
//The use syntax is different from the traditional Unix syntax, I prefer a syntax similar to
//csc, the C# compiler.
//
// grep [/h|/H] - Usage Help
//
// grep [/c] [/i] [/l] [/n] [/r] /E:reg_exp /F:files
//
// /c - print a count of matching lines for each input file;
// /i - ignore case in pattern;
// /l - print just files (scanning will stop on first match);
// /n - prefix each line of output with line number;
// /r - recursive search in subdirectories;
//
// /E:reg_exp - the Regular Expression used as search pattern. The Regular Expression can be delimited by
// quotes like "..." and '...' if you want to include in it leading or trailing blanks;
//
// /F:files - the list of input files. The files can be separated by commas as in /F:file1,file2,file3
//and wildcards can be used for their specification as in /F:*file?.txt;
//
//Example:
//
// grep /c /n /r /E:" C Sharp " /F:*.cs

namespace grep
{
    /// <summary>
    /// From : https://www.codeproject.com/Articles/1485/A-C-Grep-Application
    /// Summary description for Class1.
    /// </summary>
    class ConsoleGrep
    {
        //Option Flags
        private bool m_bRecursive;
        private bool m_bIgnoreCase;
        private bool m_bJustFiles;
        private bool m_bLineNumbers;
        private bool m_bCountLines;
        private string m_strRegEx;
        private string m_strFiles;
        //ArrayList keeping the Files
        private ArrayList m_arrFiles = new ArrayList();

        //Properties
        public bool Recursive
        {
            get { return m_bRecursive; }
            set { m_bRecursive = value; }
        }

        public bool IgnoreCase
        {
            get { return m_bIgnoreCase; }
            set { m_bIgnoreCase = value; }
        }

        public bool JustFiles
        {
            get { return m_bJustFiles; }
            set { m_bJustFiles = value; }
        }

        public bool LineNumbers
        {
            get { return m_bLineNumbers; }
            set { m_bLineNumbers = value; }
        }

        public bool CountLines
        {
            get { return m_bCountLines; }
            set { m_bCountLines = value; }
        }

        public string RegEx
        {
            get { return m_strRegEx; }
            set { m_strRegEx = value; }
        }

        public string Files
        {
            get { return m_strFiles; }
            set { m_strFiles = value; }
        }

        //Build the list of Files
        private void GetFiles(String strDir, String strExt, bool bRecursive)
        {
            //search pattern can include the wild characters '*' and '?'
            string[] fileList = Directory.GetFiles(strDir, strExt);
            for (int i = 0; i < fileList.Length; i++)
            {
                if (File.Exists(fileList[i]))
                    m_arrFiles.Add(fileList[i]);
            }
            if (bRecursive == true)
            {
                //Get recursively from subdirectories
                string[] dirList = Directory.GetDirectories(strDir);
                for (int i = 0; i < dirList.Length; i++)
                {
                    GetFiles(dirList[i], strExt, true);
                }
            }
        }

        //Search Function
        public void Search()
        {
            String strDir = Environment.CurrentDirectory;
            //First empty the list
            m_arrFiles.Clear();
            //Create recursively a list with all the files complying with the criteria
            String[] astrFiles = m_strFiles.Split(new Char[] { ',' });
            for (int i = 0; i < astrFiles.Length; i++)
            {
                //Eliminate white spaces
                astrFiles[i] = astrFiles[i].Trim();
                GetFiles(strDir, astrFiles[i], m_bRecursive);
            }
            //Now all the Files are in the ArrayList, open each one
            //iteratively and look for the search string
            String strResults = "Grep Results:\r\n\r\n";
            String strLine;
            int iLine, iCount;
            bool bEmpty = true;
            IEnumerator enm = m_arrFiles.GetEnumerator();
            while (enm.MoveNext())
            {
                try
                {
                    StreamReader sr = File.OpenText((string)enm.Current);
                    iLine = 0;
                    iCount = 0;
                    bool bFirst = true;
                    while ((strLine = sr.ReadLine()) != null)
                    {
                        iLine++;
                        //Using Regular Expressions as a real Grep
                        Match mtch;
                        if (m_bIgnoreCase == true)
                            mtch = Regex.Match(strLine, m_strRegEx, RegexOptions.IgnoreCase);
                        else
                            mtch = Regex.Match(strLine, m_strRegEx);
                        if (mtch.Success == true)
                        {
                            bEmpty = false;
                            iCount++;
                            if (bFirst == true)
                            {
                                if (m_bJustFiles == true)
                                {
                                    strResults += (string)enm.Current + "\r\n";
                                    break;
                                }
                                else
                                    strResults += (string)enm.Current + ":\r\n";
                                bFirst = false;
                            }
                            //Add the Line to Results string
                            if (m_bLineNumbers == true)
                                strResults += "  " + iLine + ": " + strLine + "\r\n";
                            else
                                strResults += "  " + strLine + "\r\n";
                        }
                    }
                    sr.Close();
                    if (bFirst == false)
                    {
                        if (m_bCountLines == true)
                            strResults += "  " + iCount + " Lines Matched\r\n";
                        strResults += "\r\n";
                    }
                }
                catch (SecurityException)
                {
                    strResults += "\r\n" + (string)enm.Current + ": Security Exception\r\n\r\n";
                }
                catch (FileNotFoundException)
                {
                    strResults += "\r\n" + (string)enm.Current + ": File Not Found Exception\r\n";
                }
            }
            if (bEmpty == true)
                Console.WriteLine("No matches found!");
            else
                Console.WriteLine(strResults);
        }

        //Print Help
        private static void PrintHelp()
        {
            Console.WriteLine("Usage: grep [/h|/H]");
            Console.WriteLine("       grep [/c] [/i] [/l] [/n] [/r] /E:reg_exp /F:files");
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main1(string[] args)
        {
            // Command line parsing
            Arguments CommandLine = new Arguments(args);
            if (CommandLine["h"] != null || CommandLine["H"] != null)
            {
                PrintHelp();
                return;
            }
            // The working object
            ConsoleGrep grep = new ConsoleGrep();
            // The arguments /e and /f are mandatory
            if (CommandLine["E"] != null)
                grep.RegEx = (string)CommandLine["E"];
            else
            {
                Console.WriteLine("Error: No Regular Expression specified!");
                Console.WriteLine();
                PrintHelp();
                return;
            }
            if (CommandLine["F"] != null)
                grep.Files = (string)CommandLine["F"];
            else
            {
                Console.WriteLine("Error: No Search Files specified!");
                Console.WriteLine();
                PrintHelp();
                return;
            }
            grep.Recursive = (CommandLine["r"] != null);
            grep.IgnoreCase = (CommandLine["i"] != null);
            grep.JustFiles = (CommandLine["l"] != null);
            if (grep.JustFiles == true)
                grep.LineNumbers = false;
            else
                grep.LineNumbers = (CommandLine["n"] != null);
            if (grep.JustFiles == true)
                grep.CountLines = false;
            else
                grep.CountLines = (CommandLine["c"] != null);
            // Do the search
            grep.Search();
        }
    }

    public class Arguments
    {
        // Variables
        //Using HybridDictionary instead of the originally StringDictionary
        private HybridDictionary Parameters;

        // Constructor
        public Arguments(string[] Args)
        {
            //Parameters=new StringDictionary();
            Parameters = new HybridDictionary();
            Regex Spliter = new Regex(@"^-{1,2}|^/|=|:", RegexOptions.IgnoreCase | RegexOptions.Compiled);
            Regex Remover = new Regex(@"^['""]?(.*?)['""]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
            string Parameter = null;
            string[] Parts;

            // Valid parameters forms:
            // {-,/,--}param{ ,=,:}((",')value(",'))
            // Examples: -param1 value1 --param2 /param3:"Test-:-work" /param4=happy -param5 '--=nice=--'
            foreach (string Txt in Args)
            {
                // Look for new parameters (-,/ or --) and a possible enclosed value (=,:)
                Parts = Spliter.Split(Txt, 3);
                switch (Parts.Length)
                {
                    // Found a value (for the last parameter found (space separator))
                    case 1:
                        if (Parameter != null)
                        {
                            if (!Parameters.Contains(Parameter))
                            {
                                Parts[0] = Remover.Replace(Parts[0], "$1");
                                Parameters.Add(Parameter, Parts[0]);
                            }
                            Parameter = null;
                        }
                        // else Error: no parameter waiting for a value (skipped)
                        break;
                    // Found just a parameter
                    case 2:
                        // The last parameter is still waiting. With no value, set it to true.
                        if (Parameter != null)
                        {
                            if (!Parameters.Contains(Parameter)) Parameters.Add(Parameter, "true");
                        }
                        Parameter = Parts[1];
                        break;
                    // Parameter with enclosed value
                    case 3:
                        // The last parameter is still waiting. With no value, set it to true.
                        if (Parameter != null)
                        {
                            if (!Parameters.Contains(Parameter)) Parameters.Add(Parameter, "true");
                        }
                        Parameter = Parts[1];
                        // Remove possible enclosing characters (",')
                        if (!Parameters.Contains(Parameter))
                        {
                            Parts[2] = Remover.Replace(Parts[2], "$1");
                            Parameters.Add(Parameter, Parts[2]);
                        }
                        Parameter = null;
                        break;
                }
            }
            // In case a parameter is still waiting
            if (Parameter != null)
            {
                if (!Parameters.Contains(Parameter)) Parameters.Add(Parameter, "true");
            }
        }

        // Retrieve a parameter value if it exists
        public object this[string Param]
        {
            get
            {
                return (Parameters[Param]);
            }
        }
    }

}
